// producer-consumer.cpp


#include <csignal>
#include <cstdio>
#include <cstdlib>
#include <ctime>

#include <chrono>
#include <condition_variable>
#include <mutex>
#include <string>
#include <thread>
#include <vector>


namespace blogs
{


using std::chrono::milliseconds;
using std::condition_variable;
using std::lock_guard;
using std::mutex;
using std::string;
using std::unique_lock;
using std::vector;


class Table
{

  private:

    vector<string>
    __buffer__;


    int
    const
    __capacity__;


    int
    __head__;


    int
    __tail__;


    int
    __count__;


    mutex
    __mtx__;


    condition_variable
    __cv__;


  public:

    Table(int cap)
        : __capacity__(cap), __buffer__(cap), __head__(0), __tail__(0), __count__(0)
    {
    }


    void
    put(string cake, string name)
    {
        unique_lock<mutex> lk(__mtx__);
        std::printf("%s puts %s\n", name.c_str(), cake.c_str());
        __cv__.wait(lk,
                    [&] {
                        bool full = __count__ == __capacity__;
                        if (full)
                            std::printf("Table is full. %s is waiting...\n", name.c_str());
                        return !full;
                    });
        __buffer__[__tail__] = cake;
        __tail__ = (__tail__ + 1) % __capacity__;
        ++__count__;
        __cv__.notify_all();
    }


    string
    take(string name)
    {
        unique_lock<mutex> lk(__mtx__);
        __cv__.wait(lk,
                    [&] {
                        bool idle = __count__ == 0;
                        if (idle)
                            std::printf("Table is empty. %s is waiting...\n", name.c_str());
                        return !idle;
                    });
        string cake(__buffer__[__head__]);
        __head__ = (__head__ + 1) % __capacity__;
        --__count__;
        __cv__.notify_all();
        std::printf("%s takes %s\n", name.c_str(), cake.c_str());
        return cake;
    }

};


class Maker
{

  private:

    static
    mutex
    __smtx__;


    static
    unsigned
    __id__;


    static
    unsigned
    __nextid__()
    {
        lock_guard<mutex> lk(__smtx__);
        return ++__id__;
    }


    string
    __name__;


  public:

    Maker(string nm)
        : __name__(nm)
    {
    }


    void
    run(Table & table)
    {
        while (true) {
            std::this_thread::sleep_for(milliseconds(std::rand()%1000));
            string cake;
            cake += "[Cake No.";
            cake += std::to_string(__nextid__());
            cake += " by ";
            cake += __name__;
            cake += "]";
            table.put(cake, __name__);
        }
    }

};


unsigned
Maker::__id__ = 0;


mutex
Maker::__smtx__;


class Eater
{

  private:

    string
    __name__;


  public:

    Eater(string nm)
        : __name__(nm)
    {
    }


    void
    run(Table & table)
    {
        while (true) {
            string cake = table.take(__name__);
            std::this_thread::sleep_for(milliseconds(std::rand()%3000));
        }
    }

};


void
bye(int sig = SIGTERM)
{
    std::printf("\nBye Producer-Consumer...\n\n");
    std::exit(sig);
}


} // end of namespace blogs


int
main(int argc, char * argv[])
{
    using std::thread;
    using blogs::Eater;
    using blogs::Maker;
    using blogs::Table;

    std::signal(SIGINT, blogs::bye);
    std::srand(std::time(0));

    Table table(3);

    Maker m1("Maker-1");
    Maker m2("Maker-2");
    Maker m3("Maker-3");
    Eater e1("Eater-1");
    Eater e2("Eater-2");
    Eater e3("Eater-3");

    thread mt1(&Maker::run, &m1, std::ref(table));
    thread mt2(&Maker::run, &m2, std::ref(table));
    thread mt3(&Maker::run, &m3, std::ref(table));
    thread et1(&Eater::run, &e1, std::ref(table));
    thread et2(&Eater::run, &e2, std::ref(table));
    thread et3(&Eater::run, &e3, std::ref(table));

    mt1.join();
    mt2.join();
    mt3.join();
    et1.join();
    et2.join();
    et3.join();

    blogs::bye();
    return 0;
}

